/*
 * A few notes on source code:
 *   ADC sampling:
 *     http://www.vk2zay.net/article/211
 *   PWM:
 *     http://www.nongnu.org/avr-libc/user-manual/group__demo__project.html
 */

#include "iocompat.h"
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))

#define IR_ADC 2  // IR sensor input
#define N0_ADC 1  // Lower neighbor input
#define N1_ADC 3  // Upper neighbor input

#define OC1A PB1  // LED Output

int i;  // Index var

int adc_val;                                  // Single ADC Conversion Output
int ir_adc_val, n0_val, n1_val;               // ADC Channel Outputs
int ir_max = 0, n0_max = 96, n1_max = 96; // Max sensor values for scaling
int n0_min = 255;                             // Min sensor value for scaling
int n1_min = 255;                             // Min sensor value for scaling

const int IR_OFFSET = 150;                    // Compensation for min value
const uint8_t IR_PERSISTENCE = 100;            // Time-step count before dimming
volatile unsigned char conversionReady;       // Conversion success indicator

// Neighbor Influence
#define NEIGHBOR_RETENTION 12
const double NEIGHBOR_SCALE_FACTOR = 0.5;     // Percentage of neighbor value used
const int    NEIGHBOR_OFFSET       = 50;      // Neighbor sensor value offset
int n0_history[NEIGHBOR_RETENTION], n1_history[NEIGHBOR_RETENTION];
long int n0_total, n1_total;
int n0_mean, n1_mean;

// PWM Control
enum pwm_intensity_input_LB {OFF_LB = 0, LOW_LB = 256, MEDIUM_LB = 512, HIGH_LB = 768};  // Lower bounds for PWM level detection
enum pwm_intensity_output {OFF = 0, LOW = 128, MEDIUM = 192, HIGH = 256};                // PWM output values
volatile uint16_t pwm = 0;        // Current PWM value for LED output
volatile uint16_t pwm_min = 0;    // Minimum PWM determined by neighbor sensors
volatile uint8_t OUTPUT_STATE = OFF;  // Current desired PWM output
volatile uint8_t ir_lag;

ISR(ADC_vect) {
  conversionReady = 1;
  ADCSRA &= (unsigned char)~_BV(ADEN);
}

ISR(TIMER1_OVF_vect) {
  if (pwm < OUTPUT_STATE - 1)
		++pwm;
	else if (pwm > OUTPUT_STATE)
		--pwm;	

  OCR = pwm;
}

void init() {
  // Initialize ADC Parameters
  //ADMUX = _BV(MUX0);    // ADC1/PB2 as initial input, LSB aligned, Vcc ref
  //ADCSRA = _BV(ADEN) | _BV(ADIE);  // enable ADC with interrupt notification

  // Initialize PWM Parameters
  TCCR1 = TIMER1_PWM_INIT | TIMER1_CLOCKSOURCE; // Timer 1 is 10-bit PWM
  OCR = 0;            // Initial value is 0
  DDROC = _BV(OC1A);  // Enable OC1A as output
  TIMSK = _BV(TOIE1); // Enable Timer 1 Overflow Interrupt

  //sleep_enable(); // Puts the device to sleep
  //sei();          // sets interrupts
}

int getADC(unsigned char channel) {
  switch(channel) {
    case 1:
      ADMUX = _BV(MUX0);
      break;
    case 2:
      ADMUX = _BV(MUX1);
      break;
    case 3:
      ADMUX = _BV(MUX1) | _BV(MUX0);
      break;
  }

  conversionReady = 0;
  //ADCSRA |= _BV(ADEN);
  ADCSRA = _BV(ADEN) | _BV(ADIE);  // enable ADC with interrupt notification
  
  set_sleep_mode(SLEEP_MODE_ADC);
  cli();
  
  while(!conversionReady) {
    sleep_enable();       // Set the Sleep-Enable bit
    sleep_bod_disable();  // Disable Brown Out Detector (BOD)
    sei();                // Sets interrupts
    sleep_cpu();          // Go to sleep
    sleep_disable();      // Disable sleep once woken up
  }
  sei();

  adc_val = ADCL;
  adc_val |= ADCH << 8;

  ADCSRA = 0;  // Disable ADCs

  return adc_val;
}

// Establish steady state data before output LEDs are activated
void establish_steady_state() {
  // Pull in dummy ADC samples to equilibriate
  for (i = 0; i < 1000; ++i) {
    getADC(IR_ADC);
    getADC(N0_ADC);
    getADC(N1_ADC);
  }

  for (i = 0; i < NEIGHBOR_RETENTION; ++i) {
    ir_adc_val = getADC(IR_ADC);
    if (ir_adc_val > ir_max)
      ir_max = ir_adc_val;

    n0_history[i] = getADC(N0_ADC);
    n1_history[i] = getADC(N1_ADC);
    n0_total += n0_history[i];
    n1_total += n1_history[i];
    if (n0_history[i] < n0_min)
      n0_min = n0_history[i];
    if (n0_history[i] > n0_max)
      n0_max = n0_history[i];
    if (n1_history[i] < n1_min)
      n1_min = n1_history[i];
    if (n1_history[i] > n1_max)
      n1_max = n1_history[i];
  }
}

int main() {
  init();
  establish_steady_state();

  i = 0;
  while(1) {
    // Sample IR sensor input
    ir_adc_val = getADC(IR_ADC);

    // Update neighbor totals
    n0_total -= n0_history[i];
    n1_total -= n1_history[i];
    n0_history[i] = getADC(N0_ADC);
    n1_history[i] = getADC(N1_ADC);
    n0_total += n0_history[i];
    n1_total += n1_history[i];

    // Recalculate the means
    n0_mean = n0_total / NEIGHBOR_RETENTION;
    n1_mean = n1_total / NEIGHBOR_RETENTION;

    // Adjust indicies
    ++i;
    if (i == NEIGHBOR_RETENTION)
      i = 0;

    // Adjust maxima & minima
    if (ir_adc_val > ir_max)
      ir_max = ir_adc_val;
    if (n0_mean < n0_min)
      n0_min = n0_mean;
    if (n1_mean < n1_min)
      n1_min = n1_mean;
    if (n0_mean > n0_max)
      n0_max = n0_mean;
    if (n1_mean > n1_max)
      n1_max = n1_mean;
    n0_val = MAX((NEIGHBOR_SCALE_FACTOR * (double)(MAX(n0_mean - n0_min,0)) / (double)(MAX(n0_max - n0_min,1)) * (double)TIMER1_TOP), 0);
    if (n0_val < (n0_min + NEIGHBOR_OFFSET))
      n0_val = 0;
    n1_val = MAX((NEIGHBOR_SCALE_FACTOR * (double)(MAX(n1_mean - n0_min,0)) / (double)(MAX(n1_max - n1_min,1)) * (double)TIMER1_TOP), 0);
    if (n1_val < (n1_min + NEIGHBOR_OFFSET))
      n1_val = 0;
    pwm_min = MAX(n0_val, n1_val);

    if ((ir_adc_val + IR_OFFSET) < ir_max)
      OUTPUT_STATE = HIGH;
    else {
      if (pwm_min > HIGH_LB)
				OUTPUT_STATE = HIGH;
			else if (pwm_min > MEDIUM_LB)
				OUTPUT_STATE = MEDIUM;
			else if (pwm_min > LOW_LB)
				OUTPUT_STATE = LOW;
			else
				OUTPUT_STATE = OFF;
    }
  }

  return 0;  // unreached
}
